package com.realshare.frame.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.*;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

/**
 * 网络请求工具
 *
 * @author realshare
 * @email 15075107780@139.com
 * @create 2018-09-17 下午 9:05
 * @modify 2018-09-17 下午 9:05
 */
public class ConnectUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(ConnectUtils.class);
    //默认编码UTF8application/msword
    private static String CHARSET = "UTF-8";
    // 连接上一个url,获取response的返回等待时间
    private static final int SOCKET_TIMEOUT = 20 * 1000;
    // 连接一个url的连接等待时间
    private static final int CONNECT_TIMEOUT = 10 * 1000;

    private static CookieStore cookieStore = null;
    private static RequestConfig requestConfig = null;
    private static SSLContext sslContext;
    private static ConnectUtils instance;

    private ConnectUtils() {
        cookieStore = new BasicCookieStore();
        // 配置超时时间（连接服务端超时10秒，请求数据返回超时20秒）
        requestConfig = RequestConfig.custom()
                .setConnectTimeout(CONNECT_TIMEOUT)
                .setSocketTimeout(SOCKET_TIMEOUT)
                .setConnectionRequestTimeout(SOCKET_TIMEOUT).build();
    }

    public static ConnectUtils http() {
        if (instance == null) {
            instance = new ConnectUtils();
        }
        return instance;
    }

    public static ConnectUtils https() {
        return https(createSSLClientDefault());
    }

    public static ConnectUtils https(String certPath, char[] password) {
        return https(getPkcs12SSLContext(certPath, password));
    }

    public static ConnectUtils https(SSLContext context) {
        if (instance == null) {
            instance = new ConnectUtils();
        }
        if (sslContext == null) {
            ConnectUtils.sslContext = context;
        }
        return instance;
    }

    public String get(String url) {
        return get(url, CHARSET);
    }

    /**
     * Get请求
     *
     * @return String
     * @since 1.0.0
     */
    public String get(String url, String charset) {
        CloseableHttpClient httpClient = getHttpClient();
        HttpGet httpGet = new HttpGet(url);
        return doRequest(httpClient, httpGet, charset);
    }

    /**
     * post请求,并使用body传输数据
     *
     * @see ContentType
     */
    public String post(String url, String body) {
        return post(url, body, null);
    }

    /**
     * post请求,并使用body传输数据
     */
    public String post(String url, String body, String charset) {
        Map<String, Object> params = new HashMap<String, Object>();
        if (StringUtils.isNotBlank(body)) {
            String[] paramList = body.split("&");
            for (String param : paramList) {
                int index = -1;
                int i = 0;
                while (i < param.length()) {
                    index = param.indexOf("=");
                    i++;
                }
                String key = param.substring(0, index);
                String value = param.substring(++index, param.length());
                params.put(key, value);
            }
        }
        return post(url, params, charset);
    }

    /**
     * UTF8编码
     *
     * @return String
     * @since 1.0.0
     */
    public String post(String url, Map<String, Object> params) {
        return post(url, params, CHARSET);
    }

    /**
     * post请求
     *
     * @return String
     * @since 1.0.0
     */
    public String post(String url, Map<String, Object> params, String charset) {
        CloseableHttpClient httpClient = getHttpClient();
        try {
            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            HttpPost httpPost = new HttpPost(url);
            for (String key : params.keySet()) {
                Object o = params.get(key);
                if (o instanceof String) {
                    //设置文本参数
                    builder.addPart(key, new StringBody((String) o, ContentType.TEXT_PLAIN));
                } else if (o instanceof File) {
                    //设置文件参数
                    builder.addPart(key, new FileBody((File) o));
                }
            }
            HttpEntity entity = builder.build();
            httpPost.setEntity(entity);
            return doRequest(httpClient, httpPost, charset);
        } catch (RuntimeException e) {
            LOGGER.error("网络请求异常,url:{},charset:{},message:{}", url, charset, e.getMessage(), e);
        }
        return null;
    }

    private CloseableHttpClient getHttpClient() {
        HttpClientBuilder builder = HttpClientBuilder.create();
        //SSL 证书
        if (sslContext != null) {
            builder.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext));
        }
        return builder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
                .setRedirectStrategy(new DefaultRedirectStrategy())
                .setDefaultRequestConfig(requestConfig)
                .setDefaultCookieStore(cookieStore).build();
    }

    private String doRequest(CloseableHttpClient httpClient, HttpUriRequest request, String charset) {
        int statusCode = 200;
        try {
            CloseableHttpResponse response = httpClient.execute(request);
            if (response != null) {
                //网络异常处理
                statusCode = response.getStatusLine().getStatusCode();
                if (statusCode == 200) {
                    HttpEntity resEntity = response.getEntity();
                    if (resEntity != null) {
                        return EntityUtils.toString(resEntity, charset);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                LOGGER.error("关闭失败", e);
            }
        }
        LOGGER.error("网络请求异常,url:{},charset:{},status:{}", request.getURI(), charset, statusCode);
        return "";
    }

    private static SSLContext createSSLClientDefault() {
        try {
            return new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                //信任所有
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            }).build();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static SSLContext getPkcs12SSLContext(String certPath, char[] password) {
        FileInputStream stream = null;
        try {
            //读取证书
            LOGGER.info("读取SSL证书:{}", certPath);
            stream = new FileInputStream(new File(certPath));
            //加载证书
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(stream, password);
            return SSLContexts.custom().loadKeyMaterial(keyStore, password).build();
        } catch (Exception e) {
            LOGGER.error("SSL证书加载失败,message:{}", e.getMessage(), e);
        } finally {
            try {
                if (stream != null) {
                    stream.close();
                }
            } catch (IOException e) {
                LOGGER.error("流关闭失败");
            }
        }
        return null;
    }
}
